import * as THREE from 'three';
import { Line2 } from 'three/examples/jsm/lines/Line2';
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry';
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial';
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2';
import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry';

import { logger } from '../../utils/logger';
import { isString } from '../../utils/utils';
import { ColorEnum, RenderOrderEnum } from '../../constants';

/**
 * Util class to statistic components and index of a scene
 */
export class StatisticsUtils {
	/**
	 * Statistics components and index of a scene
	 */
	statistic (scene) {
		const info = { components: 0, index: 0 };
		scene.traverse((object) => {
			StatisticsUtils.getObjectInfo(object, info);
		});
		return info;
	}

	/**
	 * Gets components and index number of a object (not including its children)
	 */
	static getObjectInfo (object, info) {
		// only count in Mesh and Line
		if (object instanceof THREE.Mesh || object instanceof THREE.Line) {
			info.components++;
			if (object.geometry instanceof THREE.BufferGeometry) {
				const geom = object.geometry;
				if (geom.index && geom.index.count) {
					info.index += geom.index.count;
				} else if (geom.attributes.position) {
					const pos = geom.attributes.position;
					if (pos.count && pos.itemSize) {
						info.index += Math.round(pos.count / pos.itemSize);
					}
				}
			}
		}
	}
}

export function baseDecode (data, input) {
	const baseCt = data.base.length;
	const output = [];
	const len = parseInt(input[0]); // num chars of each element
	for (let i = 1; i < input.length; i += len) {
		const str = input.substring(i, i + len).trim();
		let val = 0;
		for (let s = 0; s < str.length; s++) {
			const ind = data.base.indexOf(str[s]);
			val += ind * Math.pow(baseCt, s);
		}
		output.push(val);
	}
	return output;
}

export function floatDecode (data, input) {
	const baseCt = data.base.length;
	const baseFloatCt = data.baseFloat.length;
	let numString = '';
	for (let i = 0; i < input.length; i += 4) {
		const b90chunk = input.substring(i, i + 4).trim();
		let quotient = 0;
		for (let s = 0; s < b90chunk.length; s++) {
			const ind = data.base.indexOf(b90chunk[s]);
			quotient += ind * Math.pow(baseCt, s);
		}
		let buffer = '';
		for (let s = 0; s < 7; s++) {
			buffer = data.baseFloat[quotient % baseFloatCt] + buffer;
			quotient = parseInt(quotient / baseFloatCt);
		}
		numString += buffer;
	}
	let trailingCommas = 0;
	for (let s = 1; s < 7; s++) {
		if (numString[numString.length - s] === data.baseFloat[0]) {
			trailingCommas++;
		}
	}
	numString = numString.substring(0, numString.length - trailingCommas);
	return numString;
}

/**
 * 防止默认值
 */
export function preventDefaults (e) {
	e?.preventDefault?.();
	e?.stopPropagation?.();
}

export function throttle (callback, delay) {
	let isThrottled = false;
	let argsToUse:any[] = [];

	function next () {
		isThrottled = false;
		if (argsToUse !== null) {
			wrapper(...argsToUse); // eslint-disable-line
			argsToUse = [];
		}
	}

	function wrapper (...args) {
		if (isThrottled) {
			argsToUse = args;
			return;
		}
		isThrottled = true;
		 
		callback(...args);
		setTimeout(next, delay);
	}

	return wrapper;
}

/**
 * 删除后缀
 * @param str
 * @param suffix 默认值为空，表示删除任意后缀
 * @return {String}
 */
export function delSuffix (str, suffix = '') {
	if (!suffix) {
		const suffix = str.substring(str.lastIndexOf('.'));
		return str.replace(suffix, '');
	}
	return str.replace(suffix, '');
}

/**
 * RGB 转 十六进制
 * @param color
 * @return {*}
 */
export function toHexColor (color) {
	if (isString(color) && color.startsWith('#')) {
		return new THREE.Color(color).getHex();
	}
	return color;
}

/**
 * 十六进制转RGB
 * @param color
 * @return {*}
 */
export function hexToRgb (color) {
	return '#' + new THREE.Color(color).getHexString();
}

/**
 * 获取数组中最大值
 */
export const getMaxVal = (arr) => Math.max(...Array.from(new Set(arr)));

/**
 * 获取数组中最小值
 */
export const getMinVal = (arr) => Math.min(...Array.from(new Set(arr)));

/**
 * 带时间计算的公共处理方法
 * @param {Function} fun
 */
export const commonMethodWithTimeHandler = (fun) => {
	return new Promise(resolve => {
		const startTime = performance.now();
		fun();
		const endTime = performance.now();
		const solveTime = endTime - startTime;
		logger.log(`  ${ Math.round(solveTime) } ms: appserver request`);
		return resolve();
	});
};

/**
 * 检测是否支持GPU
 * @return {Promise<void>}
 */
export const checkGPUSupported = async () => {
	if (!navigator.gpu) throw Error('WebGPU not supported.');

	const adapter = await navigator.gpu.requestAdapter();
	if (!adapter) throw Error('Couldn’t request WebGPU adapter.');

	const device = await adapter.requestDevice();
	if (!device) throw Error('Couldn’t request WebGPU logical device.');

	logger.log('device.limits: ', device.limits);
};

/**
 * bufferGeometry合并几何体
 * @param objects
 * @return {THREE.BufferGeometry}
 */
export const mergeBufferGeometry = function (objects) {
	const sumPosArr = [];
	const sumNormArr = [];
	const sumUvArr = [];

	const modelGeometry = new THREE.BufferGeometry();

	let sumPosCursor = 0;
	let sumNormCursor = 0;
	let sumUvCursor = 0;

	let startGroupCount = 0;
	let lastGroupCount = 0;

	for (let a = 0; a < objects.length; a++) {
		const posAttArr = objects[a].geometry.getAttribute('position').array;

		for (let b = 0; b < posAttArr.length; b++) {
			sumPosArr[b + sumPosCursor] = posAttArr[b];
		}

		sumPosCursor += posAttArr.length;

		const numAttArr = objects[a].geometry.getAttribute('normal')?.array || [];

		for (let b = 0; b < numAttArr.length; b++) {
			sumNormArr[b + sumNormCursor] = numAttArr[b];
		}

		sumNormCursor += numAttArr.length;

		const uvAttArr = objects[a].geometry.getAttribute('uv')?.array || [];

		for (let b = 0; b < uvAttArr.length; b++) {
			sumUvArr[b + sumUvCursor] = uvAttArr[b];
		}

		sumUvCursor += uvAttArr.length;

		const groupArr = objects[a].geometry.groups;

		for (let b = 0; b < groupArr.length; b++) {
			startGroupCount = lastGroupCount;
			modelGeometry.addGroup(startGroupCount, groupArr[b].count, groupArr[b].materialIndex);
			lastGroupCount = startGroupCount + groupArr[b].count;
		}
	}

	modelGeometry.setAttribute('position', new THREE.Float32BufferAttribute(sumPosArr, 3));
	sumNormArr.length && modelGeometry.setAttribute('normal', new THREE.Float32BufferAttribute(sumNormArr, 3));
	sumUvArr.length && modelGeometry.setAttribute('uv', new THREE.Float32BufferAttribute(sumUvArr, 2));

	return modelGeometry;
};

/**
 * 按面获取线框
 * @param {THREE.Mesh} mesh
 * @param {HTMLCanvasElement} canvas
 * @param {Number} lineWidth
 * @param {String|Number} color
 */
export function getWireframeByFace (mesh, canvas, lineWidth = 1, color = ColorEnum.black) {
	const edges = new THREE.EdgesGeometry(mesh.geometry.clone(), 0x1535f7); // 设置边框
	const lineGeometry = new LineSegmentsGeometry().setPositions(edges.attributes.position.array);
	const lineMaterial = getLineMaterial(color, canvas, lineWidth);

	// 设置材质分辨率 (这句如果不加,宽度仍然无效，重要!!!)
	lineMaterial.resolution.set(canvas.clientWidth, canvas.clientHeight);

	// 创建 Line2
	const wireframe = new LineSegments2(lineGeometry, lineMaterial);
	return wireframe;
}

/**
 * 获取线框（多个面）
 * @param {Array<THREE.Mesh>} meshes
 * @param {HTMLCanvasElement} canvas
 * @param {Number} lineWidth
 * @param {String|Number} color
 */
export function getWireframeByFaces (meshes, canvas, lineWidth = 1, color = ColorEnum.black) {
	const positions = [];
	meshes.forEach(mesh => {
		const edges = new THREE.EdgesGeometry(mesh.geometry.clone(), 0x1535f7); // 设置边框
		positions.push(...edges.attributes.position.array);
	});

	const lineGeometry = new LineSegmentsGeometry().setPositions(positions);
	const lineMaterial = getLineMaterial(color, canvas, lineWidth);

	// 设置材质分辨率 (这句如果不加,宽度仍然无效，重要!!!)
	lineMaterial.resolution.set(canvas.clientWidth, canvas.clientHeight);

	// 创建 Line2
	const wireframe = new LineSegments2(lineGeometry, lineMaterial);
	return wireframe;
}

/**
 * 获取线材
 * @param {String|Number} color
 * @param {HTMLCanvasElement} canvas
 * @param {Number} lineWidth
 */
export function getLineMaterial (color, canvas, lineWidth) {
	const lineMaterial = new LineMaterial({
		color: new THREE.Color(color || ColorEnum.black),
		linewidth: lineWidth || 2,
		dashed: false,
		dashSize: 1,
		gapSize: 1,
		dashScale: 3,
		transparent: true,
	});
	// fix: 模型重合相交部分闪烁
	setPolygonOffset(lineMaterial);
	canvas && lineMaterial.resolution.set(canvas.clientWidth, canvas.clientHeight);
	return lineMaterial;
}

/**
 * 设置多边形偏移 (fix: 模型重合、相交部分闪烁)
 * @param material
 * @param polygonOffsetFactor   偏移系数，与相机距离
 * @param polygonOffsetUnits    偏移的单位
 */
export const setPolygonOffset = (material, polygonOffsetFactor = -1, polygonOffsetUnits = -4) => {
	if (material instanceof THREE.Material) {
		material.polygonOffset = true; // 是否使用多边形偏移，默认值为false
		// 当这两个参数都为负时（深度减小），网格将被拉向摄影机（位于前面）。 当两个参数都为正（增加深度）时，网格会被推离摄影机（会落在后面）
		material.polygonOffsetFactor = polygonOffsetFactor; // 偏移系数，与相机距离减2
		material.polygonOffsetUnits = polygonOffsetUnits;   // 偏移的单位
	}
};

/**
 * 制作线
 */
export function makeLine (canvas, wire, {
	groupName = null,
	nickname = null,
	lineWidth = 1,
	material = null,
	visible = true,
	allowSelect = false,
	renderOrder = RenderOrderEnum.line,
} = {}) {
	const result = { line: null, material: null };
	if (!wire?.length) return result;

	const wireGeometry = new LineGeometry();
	wireGeometry.setPositions(wire);
	material = material || getLineMaterial(ColorEnum.black, canvas, lineWidth);
	const line = new Line2(wireGeometry, material);
	line.computeLineDistances();
	line.scale.set(1, 1, 1);
	line.name = line.uuid;
	line.canDelete = true;
	line.visible = visible;
	line.nickname = nickname;
	line.groupName = groupName;
	line.allowSelect = allowSelect;
	line.renderOrder = renderOrder;

	result.line = line;
	result.material = material;
	return result;
}

/**
 * 按名称排序
 */
export const sortByName = (a, b) => {
	const getNum = (name) => Number(name.split('_')[1]);
	return getNum(a) > getNum(b);
};
